package org.jeecgframework.boot.image.itemOne;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;

/**
 * 炫技版超市计价与促销引擎（单文件可运行）
 * 支持：
 * 1) 任意商品、单价、数量
 * 2) 百分比折扣（按SKU/类目/谓词）
 * 3) 满减（支持“每满”或“单次”任选）
 * 4) 促销可组合叠加；货币全程 BigDecimal 精确计算
 * <p>
 * 运行： javac FruitPricingDemo.java && java FruitPricingDemo
 */
public class FruitPricingDemo {

    /* ===================== 基础建模 ===================== */

    /**
     * 货币工具：两位小数、向下舍入（财务更谨慎，也可改为 HALF_UP）
     */
    static final class Money {
        private static final int SCALE = 2;
        private final BigDecimal value;

        private Money(BigDecimal v) {
            this.value = v.setScale(SCALE, RoundingMode.DOWN);
        }

        public static Money of(double d) {
            return new Money(BigDecimal.valueOf(d));
        }

        public static Money of(BigDecimal v) {
            return new Money(v);
        }

        public Money plus(Money o) {
            return of(value.add(o.value));
        }

        public Money minus(Money o) {
            return of(value.subtract(o.value));
        }

        public Money times(BigDecimal b) {
            return of(value.multiply(b));
        }

        public BigDecimal asBig() {
            return value;
        }

        @Override
        public String toString() {
            return value.toPlainString();
        }

        @Override
        public boolean equals(Object o) {
            return (o instanceof Money) && value.compareTo(((Money) o).value) == 0;
        }

        @Override
        public int hashCode() {
            return value.hashCode();
        }
    }

    /**
     * 商品
     */
    static final class Sku {
        final String code;    // 如: APPLE
        final String name;    // 如: 苹果
        final Money unitPrice;// 单价（元/斤）

        Sku(String code, String name, Money unitPrice) {
            this.code = Objects.requireNonNull(code);
            this.name = Objects.requireNonNull(name);
            this.unitPrice = Objects.requireNonNull(unitPrice);
        }

        @Override
        public String toString() {
            return name + "(" + code + "@" + unitPrice + ")";
        }
    }

    /**
     * 购物车明细（购买重量/数量，题目是“斤”，用 BigDecimal 支持小数）
     */
    static final class LineItem {
        final Sku sku;
        final BigDecimal qty; // 斤

        LineItem(Sku sku, BigDecimal qty) {
            if (qty == null || qty.compareTo(BigDecimal.ZERO) < 0) throw new IllegalArgumentException("数量不能为负");
            this.sku = Objects.requireNonNull(sku);
            this.qty = qty;
        }

        /**
         * 未折扣小计
         */
        Money subTotal() {
            return Money.of(sku.unitPrice.asBig().multiply(qty));
        }

        @Override
        public String toString() {
            return sku.name + " x " + qty + "斤 = " + subTotal() + "元";
        }
    }

    /**
     * 促销规则接口（对总价或逐项生效，返回折后价）
     */
    interface PromotionRule {
        /**
         * 入参为原始明细列表与“当前已折后总价”，返回再次折后总价
         */
        Money apply(List<LineItem> items, Money currentTotal);

        /**
         * 规则名，仅用于日志/调试
         */
        String name();
    }

    /* ===================== 促销规则实现 ===================== */

    /**
     * 百分比折扣（如草莓 8 折）——按谓词筛选明细，单独折后再合并
     */
    static final class PercentageOffRule implements PromotionRule {
        private final String ruleName;
        private final Function<LineItem, Boolean> predicate;
        private final BigDecimal off; // 0.2=打8折；0.0=不打折

        private PercentageOffRule(String ruleName, Function<LineItem, Boolean> predicate, BigDecimal off) {
            this.ruleName = ruleName;
            this.predicate = predicate;
            this.off = off;
        }

        public static PercentageOffRule forSku(Sku sku, String ruleName, double discount /*0.8为8折*/) {
            BigDecimal off = BigDecimal.ONE.subtract(BigDecimal.valueOf(discount));
            return new PercentageOffRule(
                    ruleName,
                    li -> li.sku.code.equals(sku.code),
                    off
            );
        }

        @Override
        public Money apply(List<LineItem> items, Money currentTotal) {
            // 重新按“折扣/不折扣”拆分小计，避免重复折扣
            BigDecimal discounted = BigDecimal.ZERO;
            BigDecimal nonDiscounted = BigDecimal.ZERO;
            for (LineItem li : items) {
                BigDecimal part = li.subTotal().asBig();
                if (Boolean.TRUE.equals(predicate.apply(li))) {
                    BigDecimal after = part.multiply(BigDecimal.ONE.subtract(off)); // 折后
                    discounted = discounted.add(after);
                } else {
                    nonDiscounted = nonDiscounted.add(part);
                }
            }
            return Money.of(discounted.add(nonDiscounted));
        }

        @Override
        public String name() {
            return ruleName;
        }
    }

    /**
     * 满减：满 threshold 减 reduce，支持“是否每满”
     */
    static final class ThresholdDiscountRule implements PromotionRule {
        private final String ruleName;
        private final BigDecimal threshold; // 满 N 元
        private final BigDecimal reduce;    // 减 M 元
        private final boolean repeat;       // true=每满；false=单次

        public ThresholdDiscountRule(String ruleName, double threshold, double reduce, boolean repeat) {
            this.ruleName = ruleName;
            this.threshold = BigDecimal.valueOf(threshold);
            this.reduce = BigDecimal.valueOf(reduce);
            this.repeat = repeat;
        }

        @Override
        public Money apply(List<LineItem> items, Money currentTotal) {
            BigDecimal t = currentTotal.asBig();
            if (t.compareTo(threshold) < 0) return currentTotal;
            BigDecimal times = repeat
                    ? t.divideToIntegralValue(threshold)     // 每满
                    : BigDecimal.ONE;                        // 仅一次
            BigDecimal cut = reduce.multiply(times);
            BigDecimal after = t.subtract(cut);
            if (after.compareTo(BigDecimal.ZERO) < 0) after = BigDecimal.ZERO;
            return Money.of(after);
        }

        @Override
        public String name() {
            return ruleName + (repeat ? "(每满)" : "(单次)");
        }
    }

    /* ===================== 引擎与场景 ===================== */

    /**
     * 计价引擎：支持以声明式顺序叠加规则
     */
    static final class PricingEngine {
        private final List<PromotionRule> rules = new ArrayList<>();

        public PricingEngine addRule(PromotionRule r) {
            rules.add(r);
            return this;
        }

        public Money checkout(List<LineItem> items) {
            // 基础总价
            Money total = Money.of(items.stream().map(LineItem::subTotal)
                    .map(Money::asBig).reduce(BigDecimal.ZERO, BigDecimal::add));
            // 依次套用规则
            for (PromotionRule r : rules) {
                total = r.apply(items, total);
            }
            return total;
        }

        @Override
        public String toString() {
            if (rules.isEmpty()) return "No Rules";
            StringBuilder sb = new StringBuilder("Rules: ");
            for (int i = 0; i < rules.size(); i++) {
                if (i > 0) sb.append(" -> ");
                sb.append(rules.get(i).name());
            }
            return sb.toString();
        }
    }

    /* ===================== Demo & 简易断言 ===================== */

    // 商品定义（可视为配置/数据库）
    static final Sku APPLE = new Sku("APPLE", "苹果", Money.of(8));
    static final Sku STRAWBERRY = new Sku("STRAWBERRY", "草莓", Money.of(13));
    static final Sku MANGO = new Sku("MANGO", "芒果", Money.of(20));

    // 简易断言
    static void assertMoneyEq(Money actual, double expected, String msg) {
        Money exp = Money.of(expected);
        if (!actual.equals(exp)) {
            throw new AssertionError(msg + " 期望: " + exp + " 实际: " + actual);
        }
    }

    // 便捷构造行
    static LineItem buy(Sku sku, double jin) {
        return new LineItem(sku, BigDecimal.valueOf(jin));
    }

    public static void main(String[] args) {
        // 场景1：仅苹果/草莓，原价结算
        var items1 = List.of(
                buy(APPLE, 3),      // 3 斤
                buy(STRAWBERRY, 2)  // 2 斤
        );
        Money s1 = new PricingEngine().checkout(items1);
        // 3*8 + 2*13 = 24 + 26 = 50
        assertMoneyEq(s1, 50, "场景1");
        System.out.println("场景1 原价合计 = " + s1 + " 元");

        // 场景2：新增芒果，原价结算
        var items2 = List.of(
                buy(APPLE, 3),
                buy(STRAWBERRY, 2),
                buy(MANGO, 1.5)     // 1.5 斤
        );
        Money s2 = new PricingEngine().checkout(items2);
        // 50 + 1.5*20 = 80
        assertMoneyEq(s2, 80, "场景2");
        System.out.println("场景2 原价(含芒果) = " + s2 + " 元");

        // 场景3：草莓8折
        var engine3 = new PricingEngine()
                .addRule(PercentageOffRule.forSku(STRAWBERRY, "草莓8折", 0.8));
        Money s3 = engine3.checkout(items2);
        // 草莓(2*13)*0.8=20.8；苹果=24；芒果=30；总=74.8
        assertMoneyEq(s3, 74.80, "场景3");
        System.out.println("场景3 " + engine3 + " -> " + s3 + " 元");

        // 场景4：叠加满减（默认“每满100减10”可配置成单次）
        var engine4 = new PricingEngine()
                .addRule(PercentageOffRule.forSku(STRAWBERRY, "草莓8折", 0.8))
                .addRule(new ThresholdDiscountRule("满100减10", 100, 10, true)); // true=每满
        // 为了触发满减，把数量加大点演示
        var items4 = List.of(
                buy(APPLE, 5),         // 40
                buy(STRAWBERRY, 5),    // 原价65 -> 8折 52
                buy(MANGO, 3)          // 60
        );
        Money s4 = engine4.checkout(items4); // 折后=40+52+60=152；每满100减10 => 152-10=142
        assertMoneyEq(s4, 142, "场景4");
        System.out.println("场景4 " + engine4 + " -> " + s4 + " 元");

        System.out.println("\n全部校验通过 ");
    }
}
